use crate::http::StatusChecker;
use myutil::{err::*, *};
use serde::{Deserialize, Serialize};
use std::{collections::HashMap, fmt};

pub type Id = i32;

#[derive(Default, Debug, Clone, Deserialize)]
pub struct Contact {
    pub phone: String,
    pub im: String,
    pub email: String,
}

/// handler
pub struct Hdr<'a> {
    pub url: &'a str,
    pub token: &'a str,
}

impl<'a> Hdr<'a> {
    /// create a new instance
    pub fn new(url: &'a str, token: &'a str) -> Hdr<'a> {
        Hdr { url, token }
    }

    /// 创新一个新的 Strategy
    pub fn create_strategy(&self, strategy_detail: &str) -> Result<Id> {
        #[derive(Deserialize)]
        struct Resp {
            id: Id,
        }

        attohttpc::post(&format!("{}/strategy", self.url))
            .header("Apitoken", self.token)
            .header("Content-Type", "application/json")
            .bytes(strategy_detail.as_bytes())
            .send()
            .c(d!())?
            .check_status()
            .c(d!())?
            .json::<Resp>()
            .c(d!())
            .map(|r| r.id)
    }

    /// 创新一个新的空 Template
    pub fn create_tpl(&self, name: &str) -> Result<Id> {
        #[derive(Default, Serialize)]
        struct Req<'a> {
            parent_id: Id,
            name: &'a str,
        }

        #[derive(Deserialize)]
        struct Resp {
            id: Id,
        }

        let req = Req { parent_id: 0, name };

        attohttpc::post(&format!("{}/template", self.url))
            .header("Apitoken", self.token)
            .header("Content-Type", "application/json")
            .bytes(serde_json::to_vec(&req).c(d!())?)
            .send()
            .c(d!())?
            .check_status()
            .c(d!())?
            .json::<Resp>()
            .c(d!())
            .map(|r| r.id)
    }

    /// 删除一个 Template
    pub fn del_tpl(&self, tpl_id: Id) -> Result<()> {
        attohttpc::delete(&format!("{}/template/{}", self.url, tpl_id))
            .header("Apitoken", self.token)
            .send()
            .c(d!())?
            .check_status()
            .c(d!())
            .map(|_| ())
    }

    /// 创建一个空的 Team
    pub fn create_team(&self, name: &str) -> Result<Id> {
        #[derive(Serialize)]
        struct Req<'a> {
            team_name: &'a str,
            resume: &'a str,
            users: &'a [Id],
        }

        let req = Req {
            team_name: name,
            resume: "",
            users: &[],
        };

        attohttpc::post(&format!("{}/team", self.url))
            .header("Apitoken", self.token)
            .header("Content-Type", "application/json")
            .bytes(serde_json::to_vec(&req).c(d!())?)
            .send()
            .c(d!())?
            .check_status()
            .c(d!())
            .and_then(|_| self.get_team_id_by_name(name).c(d!()))
    }

    /// 删除一个已有的 Team
    pub fn del_team(&self, team_id: Id) -> Result<()> {
        attohttpc::delete(&format!("{}/team/{}", self.url, team_id))
            .header("Apitoken", self.token)
            .send()
            .c(d!())?
            .check_status()
            .c(d!())
            .map(|_| ())
    }

    /// 创建新用户, 初始密码: 1234
    pub fn create_user(
        &self,
        name: &str,
        cnname: &str,
        phone: &str,
        email: &str,
        im: &str,
    ) -> Result<()> {
        #[derive(Serialize)]
        struct Req<'a> {
            name: &'a str,
            cnname: &'a str,
            password: &'a str,
            phone: &'a str,
            email: &'a str,
            im: &'a str,
        }

        let req = Req {
            name,
            cnname,
            password: "1234",
            phone,
            email,
            im,
        };

        attohttpc::post(&format!("{}/user/create", self.url))
            .header("Apitoken", self.token)
            .header("Content-Type", "application/json")
            .bytes(serde_json::to_vec(&req).c(d!())?)
            .send()
            .c(d!())?
            .check_status()
            .c(d!())
            .map(|_| ())
    }

    /// 向已有 Team 中批量添加成员
    pub fn add_user_to_team(&self, team_id: Id, users: &[&str]) -> Result<()> {
        #[derive(Serialize)]
        struct Req<'a> {
            team_id: Id,
            users: &'a [&'a str],
        }

        let req = Req { team_id, users };

        attohttpc::post(&format!("{}/team/user", self.url))
            .header("Apitoken", self.token)
            .header("Content-Type", "application/json")
            .bytes(serde_json::to_vec(&req).c(d!())?)
            .send()
            .c(d!())?
            .check_status()
            .c(d!())
            .map(|_| ())
    }

    /// 向 Template 中批量添加 Team
    pub fn add_team_to_tpl(&self, tpl_id: Id, teams: &[&str]) -> Result<()> {
        #[derive(Default, Serialize)]
        struct Req {
            url: &'static str,
            uic: String,
            tpl_id: Id,
            callback: Id,
            before_callback_sms: Id,
            before_callback_mail: Id,
            after_callback_sms: Id,
            after_callback_mail: Id,
        }

        let mut req = Req::default();
        req.tpl_id = tpl_id;
        req.uic = teams.join(",");

        attohttpc::post(&format!("{}/template/action", self))
            .header("Apitoken", self.token)
            .header("Content-Type", "application/json")
            .bytes(serde_json::to_vec(&req).c(d!())?)
            .send()
            .c(d!())?
            .check_status()
            .c(d!())
            .map(|_| ())
    }

    /// 绑定 HostGroup 到 Template
    pub fn add_grp_to_tpl(&self, tpl_id: Id, grp_id: Id) -> Result<()> {
        attohttpc::post(&format!("{}/hostgroup/template", self.url))
            .header("Apitoken", self.token)
            .header("Content-Type", "application/json")
            .text(format!("{{\"tpl_id\":{},\"grp_id\":{}}}", tpl_id, grp_id))
            .send()
            .c(d!())?
            .check_status()
            .c(d!())
            .map(|_| ())
    }

    /// 通过 Team Name 查询 Team ID
    pub fn get_team_id_by_name(&self, name: &str) -> Result<Id> {
        #[derive(Deserialize)]
        struct Resp {
            id: Id,
        }

        attohttpc::get(&format!("{}/team/name/{}", self.url, name))
            .header("Apitoken", self.token)
            .send()
            .c(d!())?
            .check_status()
            .c(d!())?
            .json::<Resp>()
            .c(d!())
            .map(|r| r.id)
    }

    /// HTTP GET, 获取所有 HostGroup 列表
    pub fn get_grp_list(&self) -> Result<HashMap<String, Id>> {
        #[derive(Deserialize)]
        struct Grp {
            id: Id,
            grp_name: String,
        }
        Ok(attohttpc::get(&format!("{}/hostgroup", self.url))
            .header("Apitoken", self.token)
            .send()
            .c(d!())?
            .check_status()
            .c(d!())?
            .json::<Vec<Grp>>()
            .c(d!())?
            .into_iter()
            .map(|grp| (grp.grp_name, grp.id))
            .collect())
    }

    /// HTTP GET, 获取指定的 HostGroup 的所有 Host 列表
    pub fn get_host_list(&self, grp_id: Id) -> Result<Vec<String>> {
        #[derive(Deserialize)]
        struct Resp {
            hosts: Vec<Host>,
        }

        #[derive(Deserialize)]
        struct Host {
            hostname: String,
        }

        Ok(
            attohttpc::get(&format!("{}/hostgroup/{}", self.url, grp_id))
                .header("Apitoken", self.token)
                .send()
                .c(d!())?
                .check_status()
                .c(d!())?
                .json::<Resp>()
                .c(d!())?
                .hosts
                .into_iter()
                .map(|h| h.hostname)
                .collect(),
        )
    }

    /// HTTP DELETE, 删除指定的 HostGroup
    pub fn del_grp(&self, grp_id: Id) -> Result<()> {
        attohttpc::delete(&format!("{}/hostgroup/{}", self.url, grp_id))
            .header("Apitoken", self.token)
            .send()
            .c(d!())?
            .check_status()
            .c(d!())
            .map(|_| ())
    }

    /// HTTP POST, 创建一个新的空 HostGroup
    pub fn create_grp(&self, name: &str) -> Result<Id> {
        #[derive(Deserialize)]
        struct Grp {
            id: Id,
        }

        attohttpc::post(&format!("{}/hostgroup", self.url))
            .header("Apitoken", self.token)
            .header("Content-Type", "application/json")
            .text(format!("{{\"name\":\"{}\"}}", name))
            .send()
            .c(d!())?
            .check_status()
            .c(d!())?
            .json::<Grp>()
            .map(|grp| grp.id)
            .c(d!())
    }

    /// HTTP PATCH, 向指定的 HostGroup 中批量添加 Host
    pub fn add_hosts_to_grp(
        &self,
        grp_id: Id,
        hosts: &[String],
    ) -> Result<()> {
        #[derive(Serialize)]
        struct Req<'a> {
            action: &'static str,
            hosts: &'a [String],
        }

        let body = Req {
            action: "add",
            hosts,
        };

        attohttpc::patch(&format!("{}/hostgroup/{}/host", self.url, grp_id))
            .header("Apitoken", self.token)
            .header("Content-Type", "application/json")
            .bytes(serde_json::to_vec(&body).c(d!())?)
            .send()
            .c(d!())?
            .check_status()
            .c(d!())
            .map(|_| ())
    }

    /// HTTP PATCH, 从指定的 HostGroup 中批量删除 Host
    pub fn del_hosts_from_grp(
        &self,
        grp_id: Id,
        hosts: &[String],
    ) -> Result<()> {
        #[derive(Serialize)]
        struct Req<'a> {
            action: &'static str,
            hosts: &'a [String],
        }

        let body = Req {
            action: "remove",
            hosts,
        };

        attohttpc::patch(&format!("{}/hostgroup/{}/host", self.url, grp_id))
            .header("Apitoken", self.token)
            .header("Content-Type", "application/json")
            .bytes(serde_json::to_vec(&body).c(d!())?)
            .send()
            .c(d!())?
            .check_status()
            .c(d!())
            .map(|_| ())
    }

    /// Falcon API: template list, GET
    /// 获取所有 Template 列表
    pub fn get_tpl_list(&self) -> Result<HashMap<String, Id>> {
        #[derive(Deserialize)]
        struct Resp {
            templates: Vec<TplWrapper>,
        }

        #[derive(Deserialize)]
        struct TplWrapper {
            template: Tpl,
        }

        #[derive(Deserialize)]
        struct Tpl {
            id: Id,
            tpl_name: String,
        }

        Ok(attohttpc::get(&format!("{}/template", self.url))
            .header("Apitoken", self.token)
            .send()
            .c(d!())?
            .check_status()
            .c(d!())?
            .json::<Resp>()
            .c(d!())?
            .templates
            .into_iter()
            .map(|tpl| (tpl.template.tpl_name, tpl.template.id))
            .collect())
    }

    /// Falcon API: get hostgroups list by id, GET
    /// 获取所有已绑定指定 Template 上的 HostGroup 列表
    pub fn get_grp_list_in_tpl(&self, tpl_id: Id) -> Result<Vec<String>> {
        #[derive(Deserialize)]
        struct Resp {
            hostgroups: Vec<Grp>,
        }

        #[derive(Deserialize)]
        struct Grp {
            grp_name: String,
        }

        Ok(attohttpc::get(&format!(
            "{}/template/{}/hostgroup",
            self.url, tpl_id
        ))
        .header("Apitoken", self.token)
        .send()
        .c(d!())?
        .check_status()
        .c(d!())?
        .json::<Resp>()
        .c(d!())?
        .hostgroups
        .into_iter()
        .map(|grp| grp.grp_name)
        .collect())
    }

    /// Falcon API: get template info by id, GET
    /// 获取所有已绑定指定 Template 上的 Team 列表
    pub fn get_team_list_in_tpl(&self, tpl_id: Id) -> Result<Vec<String>> {
        #[derive(Deserialize)]
        struct Resp {
            action: Teams,
        }

        #[derive(Deserialize)]
        struct Teams {
            /// 以逗号分割的 team 名称,
            /// ex. "falcon_dev,kernel_dev,web"
            uic: String,
        }

        Ok(attohttpc::get(&format!("{}/template/{}", self.url, tpl_id))
            .header("Apitoken", self.token)
            .send()
            .c(d!())?
            .check_status()
            .c(d!())?
            .json::<Resp>()
            .c(d!())?
            .action
            .uic
            .as_str()
            .split(',')
            .map(|t| t.to_owned())
            .collect())
    }

    /// Falcon API: team list, GET
    /// 获取全局所有的 Team 列表
    pub fn get_team_list(&self) -> Result<Vec<String>> {
        #[derive(Deserialize)]
        struct Resp {
            team: Team,
        }

        #[derive(Deserialize)]
        struct Team {
            name: String,
        }

        Ok(attohttpc::get(&format!("{}/team", self.url))
            .header("Apitoken", self.token)
            .send()
            .c(d!())?
            .check_status()
            .c(d!())?
            .json::<Vec<Resp>>()
            .c(d!())?
            .into_iter()
            .map(|t| t.team.name)
            .collect())
    }

    /// Falcon API: get team info by name, GET
    /// 获取指定 Team 的内部成员的详情
    pub fn get_contact_list_in_team(
        &self,
        team_name: &str,
    ) -> Result<Vec<Contact>> {
        #[derive(Deserialize)]
        struct Resp {
            users: Vec<Contact>,
        }

        Ok(
            attohttpc::get(&format!("{}/team/name/{}", self.url, team_name))
                .header("Apitoken", self.token)
                .send()
                .c(d!())?
                .check_status()
                .c(d!())?
                .json::<Resp>()
                .c(d!())?
                .users,
        )
    }
}

impl<'a> fmt::Display for Hdr<'a> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.url)
    }
}
